/*
** 2001-09-15
**
**作者否认此源代码的版权.这里是一个祝福,而不是一个法律通知:
**    ^_^愿你行善不作恶.
**    ^_^愿你能原谅自己,原谅别人.
**    ^_^愿你自由分享,不索取多于付出.
*************************************************************************
**
 * 这个源代码文件实现了一个小型的、简单的、独立的HTTP服务器.
 * 功能:
**     * 从inetd/xinetd/stunnel4或作为独立服务器启动
**     * 每个请求一个进程
**     * 提供静态内容或运行CGI或SCGI
**     * 基于HTTP头的"Host:"属性的虚拟站点
**     * 运行于限制用户(超级域)的围栏内部
**     * CSV格式的统一日志文件
**     * 小代码库（这1个文件）,以方便安全审计
**     * 简单的设置-没有配置文件错误
 *
**这个文件实现了一个小而简单但安全有效的web服务器.
**没有虚饰.
**任何可以合理省略的都已经被删除了.
 *
** 安装规则:
**
**    (1) 像这样从inetd以root身份启动:
**
**            httpd -logfile logfile -root /home/www -user nobody
**
**        它将自动跳转到/home/www并成为用户"nobody".
**        日志文件名应该是相对于chroot的.
**
**    (2) 格式为"*.website"的目录（例如：www_sqlite_org.website）包含内容.
**        根据HTTP_HOST请求头选择目录.
**        如果没有HTTP_HOST头或者对应的主机目录不存在,则使用"default.website".
**        如果HTTP_HOST报头包含除[a-zA-Z0-9.,*~/]以外的任何字符,则生成403错误.
**
**    (3) 名称以"."或"-"开头的任何文件或目录都将被忽略,除非URL以"/.well-known/"开头,则允许使用首字母"."和"-"字符,但不允许使用首字母".".
**        RFC-5785允许letsencrypt或certbot使用webroot生成TLS证书是个例外.
**
**    (4) 文件名中除[0-9a-zA-Z,-./:_~]以外的字符和任何%HH转义字符都被翻译(转换)成"_".
**        这是对跨站点脚本攻击和其他危害的防御.
**
**    (5) 可执行文件作为CGI运行.
**        名称以".scgi"触发器和scgi请求结尾的文件(见下面的第10项).
**        所有其他文件按原样交付.
**
**    (6) 对于SSL支持,请使用stunnel并在httpd命令行上添加-https 1选项.
**
**    (7) 如果名为"-auth"的文件与要作为CGI运行或要传递的文件存在于同一目录中,则它包含HTTP基本授权的信息.
**        请参阅下面的文件格式详细信息.
**
**    (8) 要作为独立服务器运行,只需添加"-port N"命令行选项来定义要侦听的TCP端口.
**
**
**    (9) 对于静态内容,mimetype由文件后缀决定,该后缀使用下面源代码中内置的表.
**        如果有不寻常的内容文件,则可能需要扩展此表.
**
**   (10) 以".SCGI"结尾并包含"SCGI hostname port"格式文本的内容文件将格式化SCGI请求并将其发送到hostname:port,中继回复.
**        错误行为由.scgi文件的后续行决定.
**        详情请参见下面的SCGI.
**
 * 设置规则:
 *
** 命令行选项:
**
**  --root DIR       定义包含各种$HOST.website子目录的目录,每个子目录包含单个虚拟主机的web内容.
**                   如果以root启动,并且命令行上也出现"-user user",并且省略了"-jail 0",则该进程将在根在此目录下的userid user下的受限(围栏)中运行.
**                   xinetd启动需要此选项,但对于独立web服务器,此选项默认为".".
**
**  --port N         在独立模式下运行,侦听TCP端口N
**
**  --user USER      如果最初以root用户身份启动,则定义进程应在其下运行的用户.
**                   此进程将拒绝以root身份运行（出于安全考虑）.
**                   如果省略此选项并以root用户身份启动进程,它将中止而不处理任何HTTP请求.
**
**  --logfile FILE   为每个HTTP请求在文件中附加一行CSV格式的日志文件条目.
**                   文件应为完整路径名.
**                   文件名在chroot内部解释.
**                   如果文件名至少包含一个"%"且不太长,则使用strftime()展开该文件名.
**
**  --https          指示输入是通过SSL来的,并且可能是通过stunnel在上游解码的.(此程序只理解纯文本.)
**
**  --family ipv4    分别只接受来自IPV4或IPV6的输入.
**  --family ipv6    这些选项只有在alt_cn_httpd作为独立服务器运行时才有意义.
**
**  --jail BOOLEAN   指示如果最初作为根运行,是否形成受限(chroot jail).
**                   默认值为true,因此此选项唯一有用的变体是"-jail 0",它阻止受限(chroot jail)的形成.
**
**  --max-age SEC    "Cache Control:max age=%d"的值.默认为120秒.
**
**  --max-cpu SEC    每个HTTP连接允许的最大CPU时间秒数.默认值30.如果是0表示没有限制.
**
**  --debug          禁用输入超时。这对于手动输入时的调试非常有用。
**
** 命令行选项可以使用一个或两个以"-"开头的字符。所以"--debug"和"-debug"的意思是一样的。
**
** 安全功能:
**
** (1)  This program automatically puts itself inside a chroot jail if
**      it can and if not specifically prohibited by the "--jail 0"
**      command-line option.  The root of the jail is the directory that
**      contains the various $HOST.website content subdirectories.
**
** (2)  No input is read while this process has root privileges.  Root
**      privileges are dropped prior to reading any input (but after entering
**      the chroot jail, of course).  If root privileges cannot be dropped
**      (for example because the --user command-line option was omitted or
**      because the user specified by the --user option does not exist),
**      then the process aborts with an error prior to reading any input.
**
** (3)  The length of an HTTP request is limited to MAX_CONTENT_LENGTH bytes
**      (default: 250 million).  Any HTTP request longer than this fails
**      with an error.
**
** (4)  There are hard-coded time-outs on each HTTP request.  If this process
**      waits longer than the timeout for the complete request, or for CGI
**      to finish running, then this process aborts.  (The timeout feature
**      can be disabled using the --debug command-line option.)
**
** (5)  If the HTTP_HOST request header contains characters other than
**      [0-9a-zA-Z,-./:_~] then the entire request is rejected.
**
** (6)  Any characters in the URI pathname other than [0-9a-zA-Z,-./:_~]
**      are converted into "_".  This applies to the pathname only, not
**      to the query parameters or fragment.
**
** (7)  If the first character of any URI pathname component is "." or "-"
**      then a 404 Not Found reply is generated.  This prevents attacks
**      such as including ".." or "." directory elements in the pathname
**      and allows placing files and directories in the content subdirectory
**      that are invisible to all HTTP requests, by making the first
**      character of the file or subdirectory name "-" or ".".
**
** (8)  The request URI must begin with "/" or else a 404 error is generated.
**
** (9)  This program never sets the value of an environment variable to a
**      string that begins with "() {".
**
** 安全审计:
**
** This webserver mostly only serves static content.  Any security risk will
** come from CGI and SCGI.  To check an installation for security, then, it
** makes sense to focus on the CGI and SCGI scripts.
**
** To local all CGI files:
**
**          find *.website -executable -type f -print
**     OR:  find *.website -perm +0111 -type f -print
**
** The first form of the "find" command is preferred, but is only supported
** by GNU find.  On a Mac, you'll have to use the second form.
**
** To find all SCGI files:
**
**          find *.website -name '*.scgi' -type f -print
**
** If any file is a security concern, it can be disabled on a live
** installation by turning off read permissions:
**
**          chmod 0000 file-of-concern
**
** SCGI Specification Files:
**
** Content files (files without the execute bit set) that end with ".scgi"
** specify a connection to an SCGI server.  The format of the .scgi file
** follows this template:
**
**      SCGI hostname port
**      fallback: fallback-filename
**      relight: relight-command
**
** The first line specifies the location and TCP/IP port of the SCGI server
** that will handle the request.  Subsequent lines determine what to do if
** the SCGI server cannot be contacted.  If the "relight:" line is present,
** then the relight-command is run using system() and the connection is
** retried after a 1-second delay.  Use "&" at the end of the relight-command
** to run it in the background.  Make sure the relight-command does not
** send generate output, or that output will become part of the SCGI reply.
** Add a ">/dev/null" suffix (before the "&") to the relight-command if
** necessary to suppress output.  If there is no relight-command, or if the
** relight is attempted but the SCGI server still cannot be contacted, then
** the content of the fallback-filename file is returned as a substitute for
** the SCGI request.  The mimetype is determined by the suffix on the
** fallback-filename.  The fallback-filename would typically be an error
** message indicating that the service is temporarily unavailable.
**
** Basic Authorization:
**
** If the file "-auth" exists in the same directory as the content file
** (for both static content and CGI) then it contains the information used
** for basic authorization.  The file format is as follows:
**
**    *  Blank lines and lines that begin with '#' are ignored
**    *  "http-redirect" forces a redirect to HTTPS if not there already
**    *  "https-only" disallows operation in HTTP
**    *  "user NAME LOGIN:PASSWORD" checks to see if LOGIN:PASSWORD
**       authorization credentials are provided, and if so sets the
**       REMOTE_USER to NAME.
**    *  "realm TEXT" sets the realm to TEXT.
**
** There can be multiple "user" lines.  If no "user" line matches, the
** request fails with a 401 error.
**
** Because of security rule (7), there is no way for the content of the "-auth"
** file to leak out via HTTP request.
*/
#include <stdio.h>
#include <ctype.h>
#include <syslog.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pwd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <time.h>
#include <sys/times.h>
#include <netdb.h>
#include <errno.h>
#include <sys/resource.h>
#include <signal.h>
#ifdef linux
#include <sys/sendfile.h>
#endif
#include <assert.h>

/*通过设置以下宏并重新编译来配置服务器.*/
#ifndef DEFAULT_PORT
#define DEFAULT_PORT "80"             /* HTTP的默认TCP端口 */
#endif
#ifndef MAX_CONTENT_LENGTH
#define MAX_CONTENT_LENGTH 250000000  /* HTTP请求最大长度[POST影响较大]*/
#endif
#ifndef MAX_CPU
#define MAX_CPU 30                /* 最大CPU周期[秒]*/
#endif

/*我们将大多数状态信息记录为全局变量.这样就不必将信息作为参数传递给子例程,并且使可执行文件更小...*/

static char *zRoot = 0;          /* 网站根目录*/
static char *zTmpNam = 0;        /* 临时文件名*/
static char zTmpNamBuf[500];     /* 用于保存临时文件名的空间*/
static char *zProtocol = 0;      /* 浏览器正在使用的协议*/
static char *zMethod = 0;        /* HTTP请求方法(GET/POST/HEAD...).必须是GET方法*/
static char *zScript = 0;        /* 检索[取回?]的[脚本?]对象) */
static char *zRealScript = 0;    /* 要检索[取回?]的[脚本?]对象.与zScript相同,但可能会附加"/index.html"*/
static char *zHome = 0;          /* 包含内容的目录[首页的目录]) */
static char *zQueryString = 0;   /* 名称末尾的查询字符串[URLs的查询串]*/
static char *zFile = 0;          /* 要检索的对象的文件名[页面文件/静态网页/脚本文件...]*/
static int lenFile = 0;          /* zFile[文件]名称的长度*/
static char *zDir = 0;           /* 保存zFile[文件]的目录的名称*/
static char *zPathInfo = 0;      /* 经过文件的路径名的一部分*/
static char *zAgent = 0;         /* 如果浏览器正在进行此查询,则为哪种类型[浏览器代理]*/
static char *zServerName = 0;    /* 在http://后面的名称[主机域名]*/
static char *zServerPort = 0;    /* 服务端口号*/
static char *zCookie = 0;        /* 请求中报告的Cookie*/
static char *zHttpHost = 0;      /* 根据web浏览器命名[HTTP主机]*/
static char *zRealPort = 0;      /* 作为守护进程运行时的真正TCP端口*/
static char *zRemoteAddr = 0;    /* 请求的IP地址*/
static char *zReferer = 0;       /* 引用我们的页面的名称[来源引]*/
static char *zAccept = 0;        /* 接受什么格式*/
static char *zAcceptEncoding =0; /* gzip或默认值[接收的编码]*/
static char *zContentLength = 0; /* 在请求头中报告的指定的内容长度*/
static char *zContentType = 0;   /* 在请求头中报告的指定的内容数据类型*/
static char *zQuerySuffix = 0;   /* URL中第一个"?"后面的部分*/
static char *zAuthType = 0;      /* 授权类型[基本或摘要]*/
static char *zAuthArg = 0;       /* 授权值[应该是一个编码串]*/
static char *zRemoteUser = 0;    /* 授权模块设置的远程用户[REMOTE_USER]*/
static char *zIfNoneMatch= 0;    /* 请请求头中未匹配[If-None-Match]的值*/
static char *zIfModifiedSince=0; /* 请请求头中何时更改[If-Modified-Since]的值*/
static int nIn = 0;              /* 输入[流]字节数*/
static int nOut = 0;             /* 输出[流]字节数*/
static char zReplyStatus[4];     /* 响应的状态代码*/
static int statusSent = 0;       /* 发送状态行后[的值]为真*/
static char *zLogFile = 0;       /* 日志写入的文件*/
static int debugFlag = 0;        /* 如果正在调试,则为True[调试标志]*/
static struct timeval beginTime; /* 此过程开始的时间[结构]*/
static int closeConnection = 0;  /* 发送连接时为True:回复时关闭*/
static int nRequest = 0;         /* 已处理的请求数*/
static int omitLog = 0;          /* 如果为true,则不要创建日志文件条目*/
static int useHttps = 0;         /* 如果使用HTTPS:而不是HTTP:*/
static char *zHttp = "http";     /* http or https */
static int useTimeout = 1;       /* 真实使用时间[启用超时/启用闹钟软中信号监测?]*/
static int standalone = 0;       /* 作为独立服务器运行[无inetd]*/
static int ipv6Only = 0;         /* 仅使用IPv6协议*/
static int ipv4Only = 0;         /* 仅使用IPv4协议*/
static struct rusage priorSelf;  /* 上次报告SELF时间*/
static struct rusage priorChild; /* 上次报告CHILD时间*/
static int mxAge = 120;          /* 缓存控制最长时间[时效]*/
static char *default_path = "/bin:/usr/bin";  /* 默认的路径[PATH]变量*/
static char *zScgi = 0;          /* SCGI环境变量的值*/
static int rangeStart = 0;       /* 起始范围[请求]*/
static int rangeEnd = 0;         /* 结束范围[请求]*/
static int maxCpu = MAX_CPU;     /* 每进程最大的CPU占用时间片*/

/*CGI变量名和存储在全局变量中的值之间的映射.*/
static struct {
    char *zEnvName;
    char **pzEnvValue;
} cgienv[] = {
        { "CONTENT_LENGTH",          &zContentLength }, /* 必须是SCGI的第一个 */
        { "AUTH_TYPE",                   &zAuthType },
        { "AUTH_CONTENT",                &zAuthArg },
        { "CONTENT_TYPE",                &zContentType },
        { "DOCUMENT_ROOT",               &zHome },
        { "HTTP_ACCEPT",                 &zAccept },
        { "HTTP_ACCEPT_ENCODING",        &zAcceptEncoding },
        { "HTTP_COOKIE",                 &zCookie },
        { "HTTP_HOST",                   &zHttpHost },
        { "HTTP_IF_MODIFIED_SINCE",      &zIfModifiedSince },
        { "HTTP_IF_NONE_MATCH",          &zIfNoneMatch },
        { "HTTP_REFERER",                &zReferer },
        { "HTTP_USER_AGENT",             &zAgent },
        { "PATH",                        &default_path },
        { "PATH_INFO",                   &zPathInfo },
        { "QUERY_STRING",                &zQueryString },
        { "REMOTE_ADDR",                 &zRemoteAddr },
        { "REQUEST_METHOD",              &zMethod },
        { "REQUEST_URI",                 &zScript },
        { "REMOTE_USER",                 &zRemoteUser },
        { "SCGI",                        &zScgi },
        { "SCRIPT_DIRECTORY",            &zDir },
        { "SCRIPT_FILENAME",             &zFile },
        { "SCRIPT_NAME",                 &zRealScript },
        { "SERVER_NAME",                 &zServerName },
        { "SERVER_PORT",                 &zServerPort },
        { "SERVER_PROTOCOL",             &zProtocol },
};


/* 字符串中的任意双引号字符.*/
static char *Escape(char *z){
    size_t i, j;
    size_t n;
    char c;
    char *zOut;
    for(i=0; (c=z[i])!=0 && c!='"'; i++){}
    if( c==0 ) return z;
    n = 1;
    for(i++; (c=z[i])!=0; i++){ if( c=='"' ) n++; }
    zOut = malloc( i+n+1 );
    if( zOut==0 ) return "";
    for(i=j=0; (c=z[i])!=0; i++){
        zOut[j++] = c;
        if( c=='"' ) zOut[j++] = c;
    }
    zOut[j] = 0;
    return zOut;
}

/* 将结构 timeval转换为整数(以微秒为单位)*/
static long long int tvms(struct timeval *p){
    return ((long long int)p->tv_sec)*1000000 + (long long int)p->tv_usec;
}

/*在日志文件中创建一个条目.如果应该关闭HTTP连接,则终止此进程.否则返回.*/
static void MakeLogEntry(int exitCode, int lineNum){
    FILE *log;
    if( zTmpNam ){
        unlink(zTmpNam);
    }
    if( zLogFile && !omitLog ){
        struct timeval now;
        struct tm *pTm;
        struct rusage self, children;
        int waitStatus;
        char *zRM = zRemoteUser ? zRemoteUser : "";
        char *zFilename;
        size_t sz;
        char zDate[200];
        char zExpLogFile[500];

        if( zScript==0 ) zScript = "";
        if( zRealScript==0 ) zRealScript = "";
        if( zRemoteAddr==0 ) zRemoteAddr = "";
        if( zHttpHost==0 ) zHttpHost = "";
        if( zReferer==0 ) zReferer = "";
        if( zAgent==0 ) zAgent = "";
        gettimeofday(&now, 0);
        pTm = localtime(&now.tv_sec);
        strftime(zDate, sizeof(zDate), "%Y-%m-%d %H:%M:%S", pTm);
        sz = strftime(zExpLogFile, sizeof(zExpLogFile), zLogFile, pTm);
        if( sz>0 && sz<sizeof(zExpLogFile)-2 ){
            zFilename = zExpLogFile;
        }else{
            zFilename = zLogFile;
        }
        waitpid(-1, &waitStatus, WNOHANG);
        getrusage(RUSAGE_SELF, &self);
        getrusage(RUSAGE_CHILDREN, &children);
        if( (log = fopen(zFilename,"a"))!=0 ){
#ifdef COMBINED_LOG_FORMAT
            strftime(zDate, sizeof(zDate), "%d/%b/%Y:%H:%M:%S %Z", pTm);
      fprintf(log, "%s - - [%s] \"%s %s %s\" %s %d \"%s\" \"%s\"\n",
              zRemoteAddr, zDate, zMethod, zScript, zProtocol,
              zReplyStatus, nOut, zReferer, zAgent);
#else
            strftime(zDate, sizeof(zDate), "%Y-%m-%d %H:%M:%S", pTm);
            /* 日志文件的日志记录:
            **  (1) 日期时间
            **  (2) IP地址
            **  (3) 正在被访问的URL
            **  (4) 引用者
            **  (5) 响应状态码
            **  (6) 被接收字节数
            **  (7) 被发送字节数
            **  (8) 自身用户时间
            **  (9) 自身系统时间
            ** (10) 子用户时间
            ** (11) 子系统时间
            ** (12) 总挂钟时间
            ** (13) 相同TCP/IP连接的请求号
            ** (14) 用户代理
            ** (15) 远程用户
            ** (16) 与脚本名称相对应的URL字节
            ** (17) 源文件中的行号
            */
            fprintf(log,
                    "%s,%s,\"%s://%s%s\",\"%s\","
                    "%s,%d,%d,%lld,%lld,%lld,%lld,%lld,%d,\"%s\",\"%s\",%d,%d\n",
                    zDate, zRemoteAddr, zHttp, Escape(zHttpHost), Escape(zScript),
                    Escape(zReferer), zReplyStatus, nIn, nOut,
                    tvms(&self.ru_utime) - tvms(&priorSelf.ru_utime),
                    tvms(&self.ru_stime) - tvms(&priorSelf.ru_stime),
                    tvms(&children.ru_utime) - tvms(&priorChild.ru_utime),
                    tvms(&children.ru_stime) - tvms(&priorChild.ru_stime),
                    tvms(&now) - tvms(&beginTime),
                    nRequest, Escape(zAgent), Escape(zRM),
                    (int)(strlen(zHttp)+strlen(zHttpHost)+strlen(zRealScript)+3),
                    lineNum
            );
            priorSelf = self;
            priorChild = children;
#endif
            fclose(log);
            nIn = nOut = 0;
        }
    }
    if( closeConnection ){
        exit(exitCode);
    }
    statusSent = 0;
}

/* 安全分配内存*/
static char *SafeMalloc( size_t size ){
    char *p;

    p = (char*)malloc(size);
    if( p==0 ){
        strcpy(zReplyStatus, "998");
        /* LOG: 内存分配(Malloc())失败 */
        MakeLogEntry(1,100);
        exit(1);
    }
    return p;
}

/*将环境变量zVar的值设置为zValue.*/
static void SetEnv(const char *zVar, const char *zValue){
    char *z;
    size_t len;
    if( zValue==0 ) zValue="";
    /* 禁用尝试的门攻击 */
    if( strncmp(zValue,"() {",4)==0 ) zValue = "";
    len = strlen(zVar) + strlen(zValue) + 2;
    z = SafeMalloc(len);
    sprintf(z,"%s=%s",zVar,zValue);
    putenv(z);
}

/*
 * 取第一个元素(从一个空格往左取)
 * 从字符串中删除第一个空格分隔的标记并返回指向它的指针.向字符串中添加NULL以终止标记.使*zLeftOver指向下一个标记的开头.
 * */
static char *GetFirstElement(char *zInput, char **zLeftOver){
    char *zResult = 0;
    if( zInput==0 ){
        if( zLeftOver ) *zLeftOver = 0;
        return 0;
    }
    while( isspace(*(unsigned char*)zInput) ){ zInput++; }
    zResult = zInput;
    while( *zInput && !isspace(*(unsigned char*)zInput) ){ zInput++; }
    if( *zInput ){
        *zInput = 0;
        zInput++;
        while( isspace(*(unsigned char*)zInput) ){ zInput++; }
    }
    if( zLeftOver ){ *zLeftOver = zInput; }
    return zResult;
}

/*将字符串复制到从malloc获得的内存中.*/
static char *StrDup(const char *zSrc){
    char *zDest;
    size_t size;

    if( zSrc==0 ) return 0;
    size = strlen(zSrc) + 1;
    zDest = (char*)SafeMalloc( size );
    strcpy(zDest,zSrc);
    return zDest;
}
static char *StrAppend(char *zPrior, const char *zSep, const char *zSrc){
    char *zDest;
    size_t size;
    size_t n0, n1, n2;

    if( zSrc==0 ) return 0;
    if( zPrior==0 ) return StrDup(zSrc);
    n0 = strlen(zPrior);
    n1 = strlen(zSep);
    n2 = strlen(zSrc);
    size = n0+n1+n2+1;
    zDest = (char*)SafeMalloc( size );
    memcpy(zDest, zPrior, n0);
    free(zPrior);
    memcpy(&zDest[n0],zSep,n1);
    memcpy(&zDest[n0+n1],zSrc,n2+1);
    return zDest;
}

/*
** 比较两个ETag值。如果匹配则返回0，如果不同则返回非0。
**
** 左边的一个可能是空指针，它可能会被引用。
*/
static int CompareEtags(const char *zA, const char *zB){
    if( zA==0 ) return 1;
    if( zA[0]=='"' ){
        int lenB = (int)strlen(zB);
        if( strncmp(zA+1, zB, lenB)==0 && zA[lenB+1]=='"' ) return 0;
    }
    return strcmp(zA, zB);
}

/*
** 在看到的第一个\n或\r字符处换行。
*/
static void RemoveNewline(char *z){
    if( z==0 ) return;
    while( *z && *z!='\n' && *z!='\r' ){ z++; }
    *z = 0;
}

/* 以RFC822日期字符串的形式呈现自1970年以来的秒数。在静态缓冲区中返回指向该字符串的指针。*/
static char *Rfc822Date(time_t t){
    struct tm *tm;
    static char zDate[100];
    tm = gmtime(&t);
    strftime(zDate, sizeof(zDate), "%a, %d %b %Y %H:%M:%S %Z", tm);
    return zDate;
}

/*在标题中打印日期标记。标签的名称是zTag。日期由给定的unix时间戳确定。*/
static int DateTag(const char *zTag, time_t t){
    return printf("%s: %s\r\n", zTag, Rfc822Date(t));
}

/* 按照我们期望的那样，从HTTP解析RFC822格式的时间戳，并返回Unix epoch时间。<=失败时返回零。*/
time_t ParseRfc822Date(const char *zDate){
    int mday, mon, year, yday, hour, min, sec;
    char zIgnore[4];
    char zMonth[4];
    static const char *const azMonths[] =
            {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
                  &mday, zMonth, &year, &hour, &min, &sec)){
        if( year > 1900 ) year -= 1900;
        for(mon=0; mon<12; mon++){
            if( !strncmp( azMonths[mon], zMonth, 3 )){
                int nDay;
                int isLeapYr;
                static int priorDays[] =
                        {  0, 31, 59, 90,120,151,181,212,243,273,304,334 };
                isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0);
                yday = priorDays[mon] + mday - 1;
                if( isLeapYr && mon>1 ) yday++;
                nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday;
                return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec;
            }
        }
    }
    return 0;
}

/* 解析RFC822标准的日期格式(ParseRfc822Date)的测试程序*/
void TestParseRfc822Date(void){
    time_t t1, t2;
    for(t1=0; t1<0x7fffffff; t1 += 127){
        t2 = ParseRfc822Date(Rfc822Date(t1));
        assert( t1==t2 );
    }
}

/* 打印响应的第一行,后跟服务器类型.*/
static void StartResponse(const char *zResultCode){
    time_t now;
    time(&now);
    if( statusSent ) return;
    nOut += printf("%s %s\r\n", zProtocol, zResultCode);
    strncpy(zReplyStatus, zResultCode, 3);
    zReplyStatus[3] = 0;
    if( zReplyStatus[0]>='4' ){
        closeConnection = 1;
    }
    if( closeConnection ){
        nOut += printf("Connection: close\r\n");
    }else{
        nOut += printf("Connection: keep-alive\r\n");
    }
    nOut += DateTag("Date", now);
    statusSent = 1;
}

/* 告诉客户没有这样的文件*/
static void NotFound(int lineno){
    StartResponse("404 Not Found");
    nOut += printf(
            "Content-type: text/html; charset=utf-8\r\n"
            "\r\n"
            "<head><title lineno=\"%d\">Not Found</title></head>\n"
            "<body><h1>未找到文档</h1>\n"
            "文档%s在此服务器上不可用\n"
            "</body>\n", lineno, zScript);
    MakeLogEntry(0, lineno);
    exit(0);
}

/*告诉客户他们在这里(被禁用)不受欢迎.*/
static void Forbidden(int lineno){
    StartResponse("403 Forbidden");
    nOut += printf(
            "Content-type: text/plain; charset=utf-8\r\n"
            "\r\n"
            "Access denied\n"
    );
    closeConnection = 1;
    MakeLogEntry(0, lineno);
    exit(0);
}

/* 告诉客户访问文档需要授权.*/
static void NotAuthorized(const char *zRealm){
    StartResponse("401 Authorization Required");
    nOut += printf(
            "WWW-Authenticate: Basic realm=\"%s\"\r\n"
            "Content-type: text/html; charset=utf-8\r\n"
            "\r\n"
            "<head><title>未授权</title></head>\n"
            "<body><h1>401未授权</h1>\n"
            "此文档需要登录名和密码\n"
            "</body>\n", zRealm);
    /* LOG: 未授权 */
    MakeLogEntry(0, 110);
}

/* 告诉客户端脚本中有错误.*/
static void CgiError(void){
    StartResponse("500 Error");
    nOut += printf(
            "Content-type: text/html; charset=utf-8\r\n"
            "\r\n"
            "<head><title>CGI程序错误</title></head>\n"
            "<body><h1>CGI程序错误</h1>\n"
            "CGI程序%s产生了一个错误\n"
            "</body>\n", zScript);
    /* LOG: CGI程序错误 */
    MakeLogEntry(0, 120);
    exit(0);
}

/*如果我们超时或捕捉到其他类型的信号,就会调用这个函数.记录一个900+iSig的错误代码,然后退出.*/
static void Timeout(int iSig){
    if( !debugFlag ){
        if( zScript && zScript[0] ){
            char zBuf[10];
            zBuf[0] = '9';
            zBuf[1] = '0' + (iSig/10)%10;
            zBuf[2] = '0' + iSig%10;
            zBuf[3] = 0;
            strcpy(zReplyStatus, zBuf);
            MakeLogEntry(0, 130);  /* LOG: Timeout */
        }
        exit(0);
    }
}

/* 告诉客户端脚本中有错误.*/
static void CgiScriptWritable(void){
    StartResponse("500 CGI配置错误");
    nOut += printf(
            "Content-type: text/plain; charset=utf-8\r\n"
            "\r\n"
            "CGI程序%s可由其所有者以外的用户写入。\n",
            zRealScript);
    /* LOG: CGI脚本是可写的 */
    MakeLogEntry(0, 140);
    exit(0);
}

/* 告诉客户端服务器出现故障.*/
static void Malfunction(int linenum, const char *zFormat, ...){
    va_list ap;
    va_start(ap, zFormat);
    StartResponse("500 服务器故障");
    nOut += printf("Content-type: text/plain; charset=utf-8\r\n\r\nWeb服务器故障; 错误号 %d\n\n", linenum);
    if( zFormat ){
        nOut += vprintf(zFormat, ap);
        printf("\n");
        nOut++;
    }
    MakeLogEntry(0, linenum);
    exit(0);
}

/*
 * 对指定的文档执行服务器重定向.
 * 文档名称不包含方案、网络位置或查询字符串.
 * 它将只是一条路径.
*/
static void Redirect(const char *zPath, int iStatus, int finish, int lineno){
    switch( iStatus ){
        case 301:
            StartResponse("301 永久重定向");
            break;
        case 308:
            StartResponse("308 永久重定向");
            break;
        default:
            StartResponse("302 临时重定向");
            break;
    }
    if( zServerPort==0 || zServerPort[0]==0 || strcmp(zServerPort,"80")==0 ){
        nOut += printf("Location: %s://%s%s%s\r\n",
                       zHttp, zServerName, zPath, zQuerySuffix);
    }else{
        nOut += printf("Location: %s://%s:%s%s%s\r\n",
                       zHttp, zServerName, zServerPort, zPath, zQuerySuffix);
    }
    if( finish ){
        nOut += printf("Content-length: 0\r\n");
        nOut += printf("\r\n");
        MakeLogEntry(0, lineno);
    }
    fflush(stdout);
}

/*此函数将其输入视为base64编码字符串,并返回该字符串的解码值.将忽略输入中无效的base64字符(如空格和换行符).*/
void Decode64(char *z64){
    char *zData;
    int n64;
    int i, j;
    int a, b, c, d;
    static int isInit = 0;
    static int trans[128];
    static unsigned char zBase[] ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    if( !isInit ){
        for(i=0; i<128; i++){ trans[i] = 0; }
        for(i=0; zBase[i]; i++){ trans[zBase[i] & 0x7f] = i; }
        isInit = 1;
    }
    n64 = strlen(z64);
    while( n64>0 && z64[n64-1]=='=' ) n64--;
    zData = z64;
    for(i=j=0; i+3<n64; i+=4){
        a = trans[z64[i] & 0x7f];
        b = trans[z64[i+1] & 0x7f];
        c = trans[z64[i+2] & 0x7f];
        d = trans[z64[i+3] & 0x7f];
        zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
        zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
        zData[j++] = ((c<<6) & 0xc0) | (d & 0x3f);
    }
    if( i+2<n64 ){
        a = trans[z64[i] & 0x7f];
        b = trans[z64[i+1] & 0x7f];
        c = trans[z64[i+2] & 0x7f];
        zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
        zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
    }else if( i+1<n64 ){
        a = trans[z64[i] & 0x7f];
        b = trans[z64[i+1] & 0x7f];
        zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
    }
    zData[j] = 0;
}

/*
 * 检查是否根据zAuthFile文件中的信息为用户提供了基本的授权凭证.如果授权,则返回true.未授权返回false.
 *
 * 文件格式:
 *
 * --忽略空行和以"#"开头的行
 * --"http重定向(http-redirect)"强制重定向到HTTPS(如果还没有)
 * --"仅限https(https-only)"不允许在HTTP中进行操作
 * --"登录用户名:密码(user NAME LOGIN:PASSWORD)"检查是否提供了LOGIN:PASSWORD授权凭据,如果提供了,则将远程用户(REMOTE_USER)设置为NAME.
 * --"领域文本(realm TEXT)"将领域设置为文本(TEXT).
 * --"任何人(anyone)"绕过身份验证,允许任何人查看文件.与"http(http-redirect)重定向"结合使用很有用
*/
static int CheckBasicAuthorization(const char *zAuthFile){
    FILE *in;
    char *zRealm = "unknown realm";
    char *zLoginPswd;
    char *zName;
    char zLine[2000];

    in = fopen(zAuthFile, "rb");
    if( in==0 ){
        /* LOG: Cannot open -auth file */
        NotFound(150);
        return 0;
    }
    if( zAuthArg ) Decode64(zAuthArg);
    while( fgets(zLine, sizeof(zLine), in) ){
        char *zFieldName;
        char *zVal;

        zFieldName = GetFirstElement(zLine,&zVal);
        if( zFieldName==0 || *zFieldName==0 ) continue;
        if( zFieldName[0]=='#' ) continue;
        RemoveNewline(zVal);
        if( strcmp(zFieldName, "realm")==0 ){
            zRealm = StrDup(zVal);
        }else if( strcmp(zFieldName,"user")==0 ){
            if( zAuthArg==0 ) continue;
            zName = GetFirstElement(zVal, &zVal);
            zLoginPswd = GetFirstElement(zVal, &zVal);
            if( zLoginPswd==0 ) continue;
            if( zAuthArg && strcmp(zAuthArg,zLoginPswd)==0 ){
                zRemoteUser = StrDup(zName);
                fclose(in);
                return 1;
            }
        }else if( strcmp(zFieldName,"https-only")==0 ){
            if( !useHttps ){
                /* LOG:  http request on https-only page */
                NotFound(160);
                fclose(in);
                return 0;
            }
        }else if( strcmp(zFieldName,"http-redirect")==0 ){
            if( !useHttps ){
                zHttp = "https";
                /* LOG: -auth redirect */
                Redirect(zScript, 301, 1, 170);
                fclose(in);
                return 0;
            }
        }else if( strcmp(zFieldName,"anyone")==0 ){
            fclose(in);
            return 1;
        }else{
            /* LOG: -auth文件中的条目格式不正确 */
            NotFound(180);
            fclose(in);
            return 0;
        }
    }
    fclose(in);
    NotAuthorized(zRealm);
    return 0;
}

/* 根据文档的名称猜测其mime类型.*/
const char *GetMimeType(const char *zName, int nName){
    const char *z;
    int i;
    int first, last;
    int len;
    char zSuffix[20];

    /*
     * 基于文件后缀的模拟类型(mimetypes)表.
     * 后缀必须按顺序排序,这样我们就可以进行二进制搜索来找到mime类型
    */
    static const struct {
        const char *zSuffix;       /* 文件后缀 */
        int size;                  /* 后缀长度 */
        const char *zMimetype;     /* 对应的mimetype */
    } aMime[] = {
            { "ai",         2, "application/postscript"            },
            { "aif",        3, "audio/x-aiff"                      },
            { "aifc",       4, "audio/x-aiff"                      },
            { "aiff",       4, "audio/x-aiff"                      },
            { "arj",        3, "application/x-arj-compressed"      },
            { "asc",        3, "text/plain"                        },
            { "asf",        3, "video/x-ms-asf"                    },
            { "asx",        3, "video/x-ms-asx"                    },
            { "au",         2, "audio/ulaw"                        },
            { "avi",        3, "video/x-msvideo"                   },
            { "bat",        3, "application/x-msdos-program"       },
            { "bcpio",      5, "application/x-bcpio"               },
            { "bin",        3, "application/octet-stream"          },
            { "c",          1, "text/plain"                        },
            { "cc",         2, "text/plain"                        },
            { "ccad",       4, "application/clariscad"             },
            { "cdf",        3, "application/x-netcdf"              },
            { "class",      5, "application/octet-stream"          },
            { "cod",        3, "application/vnd.rim.cod"           },
            { "com",        3, "application/x-msdos-program"       },
            { "cpio",       4, "application/x-cpio"                },
            { "cpt",        3, "application/mac-compactpro"        },
            { "csh",        3, "application/x-csh"                 },
            { "css",        3, "text/css"                          },
            { "dcr",        3, "application/x-director"            },
            { "deb",        3, "application/x-debian-package"      },
            { "dir",        3, "application/x-director"            },
            { "dl",         2, "video/dl"                          },
            { "dms",        3, "application/octet-stream"          },
            { "doc",        3, "application/msword"                },
            { "drw",        3, "application/drafting"              },
            { "dvi",        3, "application/x-dvi"                 },
            { "dwg",        3, "application/acad"                  },
            { "dxf",        3, "application/dxf"                   },
            { "dxr",        3, "application/x-director"            },
            { "eps",        3, "application/postscript"            },
            { "etx",        3, "text/x-setext"                     },
            { "exe",        3, "application/octet-stream"          },
            { "ez",         2, "application/andrew-inset"          },
            { "f",          1, "text/plain"                        },
            { "f90",        3, "text/plain"                        },
            { "fli",        3, "video/fli"                         },
            { "flv",        3, "video/flv"                         },
            { "gif",        3, "image/gif"                         },
            { "gl",         2, "video/gl"                          },
            { "gtar",       4, "application/x-gtar"                },
            { "gz",         2, "application/x-gzip"                },
            { "hdf",        3, "application/x-hdf"                 },
            { "hh",         2, "text/plain"                        },
            { "hqx",        3, "application/mac-binhex40"          },
            { "h",          1, "text/plain"                        },
            { "htm",        3, "text/html; charset=utf-8"          },
            { "html",       4, "text/html; charset=utf-8"          },
            { "ice",        3, "x-conference/x-cooltalk"           },
            { "ief",        3, "image/ief"                         },
            { "iges",       4, "model/iges"                        },
            { "igs",        3, "model/iges"                        },
            { "ips",        3, "application/x-ipscript"            },
            { "ipx",        3, "application/x-ipix"                },
            { "jad",        3, "text/vnd.sun.j2me.app-descriptor"  },
            { "jar",        3, "application/java-archive"          },
            { "jpeg",       4, "image/jpeg"                        },
            { "jpe",        3, "image/jpeg"                        },
            { "jpg",        3, "image/jpeg"                        },
            { "js",         2, "application/x-javascript"          },
            { "kar",        3, "audio/midi"                        },
            { "latex",      5, "application/x-latex"               },
            { "lha",        3, "application/octet-stream"          },
            { "lsp",        3, "application/x-lisp"                },
            { "lzh",        3, "application/octet-stream"          },
            { "m",          1, "text/plain"                        },
            { "m3u",        3, "audio/x-mpegurl"                   },
            { "man",        3, "application/x-troff-man"           },
            { "me",         2, "application/x-troff-me"            },
            { "mesh",       4, "model/mesh"                        },
            { "mid",        3, "audio/midi"                        },
            { "midi",       4, "audio/midi"                        },
            { "mif",        3, "application/x-mif"                 },
            { "mime",       4, "www/mime"                          },
            { "movie",      5, "video/x-sgi-movie"                 },
            { "mov",        3, "video/quicktime"                   },
            { "mp2",        3, "audio/mpeg"                        },
            { "mp2",        3, "video/mpeg"                        },
            { "mp3",        3, "audio/mpeg"                        },
            { "mpeg",       4, "video/mpeg"                        },
            { "mpe",        3, "video/mpeg"                        },
            { "mpga",       4, "audio/mpeg"                        },
            { "mpg",        3, "video/mpeg"                        },
            { "ms",         2, "application/x-troff-ms"            },
            { "msh",        3, "model/mesh"                        },
            { "nc",         2, "application/x-netcdf"              },
            { "oda",        3, "application/oda"                   },
            { "ogg",        3, "application/ogg"                   },
            { "ogm",        3, "application/ogg"                   },
            { "pbm",        3, "image/x-portable-bitmap"           },
            { "pdb",        3, "chemical/x-pdb"                    },
            { "pdf",        3, "application/pdf"                   },
            { "pgm",        3, "image/x-portable-graymap"          },
            { "pgn",        3, "application/x-chess-pgn"           },
            { "pgp",        3, "application/pgp"                   },
            { "pl",         2, "application/x-perl"                },
            { "pm",         2, "application/x-perl"                },
            { "png",        3, "image/png"                         },
            { "pnm",        3, "image/x-portable-anymap"           },
            { "pot",        3, "application/mspowerpoint"          },
            { "ppm",        3, "image/x-portable-pixmap"           },
            { "pps",        3, "application/mspowerpoint"          },
            { "ppt",        3, "application/mspowerpoint"          },
            { "ppz",        3, "application/mspowerpoint"          },
            { "pre",        3, "application/x-freelance"           },
            { "prt",        3, "application/pro_eng"               },
            { "ps",         2, "application/postscript"            },
            { "qt",         2, "video/quicktime"                   },
            { "ra",         2, "audio/x-realaudio"                 },
            { "ram",        3, "audio/x-pn-realaudio"              },
            { "rar",        3, "application/x-rar-compressed"      },
            { "ras",        3, "image/cmu-raster"                  },
            { "ras",        3, "image/x-cmu-raster"                },
            { "rgb",        3, "image/x-rgb"                       },
            { "rm",         2, "audio/x-pn-realaudio"              },
            { "roff",       4, "application/x-troff"               },
            { "rpm",        3, "audio/x-pn-realaudio-plugin"       },
            { "rtf",        3, "application/rtf"                   },
            { "rtf",        3, "text/rtf"                          },
            { "rtx",        3, "text/richtext"                     },
            { "scm",        3, "application/x-lotusscreencam"      },
            { "set",        3, "application/set"                   },
            { "sgml",       4, "text/sgml"                         },
            { "sgm",        3, "text/sgml"                         },
            { "sh",         2, "application/x-sh"                  },
            { "shar",       4, "application/x-shar"                },
            { "silo",       4, "model/mesh"                        },
            { "sit",        3, "application/x-stuffit"             },
            { "skd",        3, "application/x-koan"                },
            { "skm",        3, "application/x-koan"                },
            { "skp",        3, "application/x-koan"                },
            { "skt",        3, "application/x-koan"                },
            { "smi",        3, "application/smil"                  },
            { "smil",       4, "application/smil"                  },
            { "snd",        3, "audio/basic"                       },
            { "sol",        3, "application/solids"                },
            { "spl",        3, "application/x-futuresplash"        },
            { "src",        3, "application/x-wais-source"         },
            { "step",       4, "application/STEP"                  },
            { "stl",        3, "application/SLA"                   },
            { "stp",        3, "application/STEP"                  },
            { "sv4cpio",    7, "application/x-sv4cpio"             },
            { "sv4crc",     6, "application/x-sv4crc"              },
            { "svg",        3, "image/svg+xml"                     },
            { "swf",        3, "application/x-shockwave-flash"     },
            { "t",          1, "application/x-troff"               },
            { "tar",        3, "application/x-tar"                 },
            { "tcl",        3, "application/x-tcl"                 },
            { "tex",        3, "application/x-tex"                 },
            { "texi",       4, "application/x-texinfo"             },
            { "texinfo",    7, "application/x-texinfo"             },
            { "tgz",        3, "application/x-tar-gz"              },
            { "tiff",       4, "image/tiff"                        },
            { "tif",        3, "image/tiff"                        },
            { "tr",         2, "application/x-troff"               },
            { "tsi",        3, "audio/TSP-audio"                   },
            { "tsp",        3, "application/dsptype"               },
            { "tsv",        3, "text/tab-separated-values"         },
            { "txt",        3, "text/plain"                        },
            { "unv",        3, "application/i-deas"                },
            { "ustar",      5, "application/x-ustar"               },
            { "vcd",        3, "application/x-cdlink"              },
            { "vda",        3, "application/vda"                   },
            { "viv",        3, "video/vnd.vivo"                    },
            { "vivo",       4, "video/vnd.vivo"                    },
            { "vrml",       4, "model/vrml"                        },
            { "vsix",       4, "application/vsix"                  },
            { "wav",        3, "audio/x-wav"                       },
            { "wax",        3, "audio/x-ms-wax"                    },
            { "wiki",       4, "application/x-fossil-wiki"         },
            { "wma",        3, "audio/x-ms-wma"                    },
            { "wmv",        3, "video/x-ms-wmv"                    },
            { "wmx",        3, "video/x-ms-wmx"                    },
            { "wrl",        3, "model/vrml"                        },
            { "wvx",        3, "video/x-ms-wvx"                    },
            { "xbm",        3, "image/x-xbitmap"                   },
            { "xlc",        3, "application/vnd.ms-excel"          },
            { "xll",        3, "application/vnd.ms-excel"          },
            { "xlm",        3, "application/vnd.ms-excel"          },
            { "xls",        3, "application/vnd.ms-excel"          },
            { "xlw",        3, "application/vnd.ms-excel"          },
            { "xml",        3, "text/xml"                          },
            { "xpm",        3, "image/x-xpixmap"                   },
            { "xwd",        3, "image/x-xwindowdump"               },
            { "xyz",        3, "chemical/x-pdb"                    },
            { "zip",        3, "application/zip"                   },
    };

    for(i=nName-1; i>0 && zName[i]!='.'; i--){}
    z = &zName[i+1];
    len = nName - i;
    if( len<(int)sizeof(zSuffix)-1 ){
        strcpy(zSuffix, z);
        for(i=0; zSuffix[i]; i++) zSuffix[i] = tolower(zSuffix[i]);
        first = 0;
        last = sizeof(aMime)/sizeof(aMime[0]);
        while( first<=last ){
            int c;
            i = (first+last)/2;
            c = strcmp(zSuffix, aMime[i].zSuffix);
            if( c==0 ) return aMime[i].zMimetype;
            if( c<0 ){
                last = i-1;
            }else{
                first = i+1;
            }
        }
    }
    return "application/octet-stream";
}

/*
 * 下表包含1,表示在查询参数和片段之前的URL部分中允许的所有字符.
 * 被允许的字符: 0-9a-zA-Z,-./:_~
 * 不被允许字符包括: !"#$%&'()*+;<=>?[\]^{|}
*/
static const char allowedInName[] = {
        /*  x0  x1  x2  x3  x4  x5  x6  x7  x8  x9  xa  xb  xc  xd  xe  xf */
/* 0x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* 1x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* 2x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
/* 3x */   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,
/* 4x */   0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
/* 5x */   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  1,
/* 6x */   0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
/* 7x */   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  1,  0,
/* 8x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* 9x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Ax */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Bx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Cx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Dx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Ex */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Fx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
};

/*
 * 转换特殊字符为下划线.
 *
 * 删除输入字符串z[]中所有不允许的字符.将任何不允许的字符转换为"_".
 * 不是说X是任何字节的三字符序列"%XX"被转换成一个"_"字符.
 * 返回已转换的字符数."%XX"->"_"转换计数为单个字符.
 */
static int sanitizeString(char *z){
    int nChange = 0;
    while( *z ){
        if( !allowedInName[*(unsigned char*)z] ){
            if( *z=='%' && z[1]!=0 && z[2]!=0 ){
                int i;
                for(i=3; (z[i-2] = z[i])!=0; i++){}
            }
            *z = '_';
            nChange++;
        }
        z++;
    }
    return nChange;
}

/*计算字符串中正斜杆("/")字符的数目.*/
static int countSlashes(const char *z){
    int n = 0;
    while( *z ) if( *(z++)=='/' ) n++;
    return n;
}

/*
* 在第一次从输入流(in)丢弃nSkip字节之后,将nXfer字节从输入流(in)传输到输出管道(out).
* 根据被传输的字节数递增传输长度(nOut)全局变量.
*/
static void xferBytes(FILE *in, FILE *out, int nXfer, int nSkip){
    size_t n;
    size_t got;
    char zBuf[16384];
    while( nSkip>0 ){
        n = nSkip;
        /*取实际读取的缓冲区长度*/
        if( n>sizeof(zBuf) ) n = sizeof(zBuf);
        got = fread(zBuf, 1, n, in);
        if( got==0 ) break;
        nSkip -= got;
    }
    while( nXfer>0 ){
        n = nXfer;
        if( n>sizeof(zBuf) ) n = sizeof(zBuf);
        got = fread(zBuf, 1, n, in);
        if( got==0 ) break;
        fwrite(zBuf, got, 1, out);
        nOut += got;
        nXfer -= got;
    }
}


/*
 * 将zFile指定的文件的文本作为答复发送.
 * 使用zFile名称末尾的后缀来确定mimetype.
 *
 * 返回1以省略为响应创建日志条目.
*/
static int SendFile(
        const char *zFile,      /* 要发送的文件的名称 */
        int lenFile,            /* zFile名称的长度(字节) */
        struct stat *pStat      /* stat函数读取zFile信息的结果 */
){
    const char *zContentType;
    time_t t;
    FILE *in;
    char zETag[100];

    zContentType = GetMimeType(zFile, lenFile);
    if( zTmpNam ) unlink(zTmpNam);
    sprintf(zETag, "m%xs%x", (int)pStat->st_mtime, (int)pStat->st_size);
    if( CompareEtags(zIfNoneMatch,zETag)==0 || (zIfModifiedSince!=0 && (t = ParseRfc822Date(zIfModifiedSince))>0 && t>=pStat->st_mtime)){
        StartResponse("304 Not Modified");
        nOut += DateTag("Last-Modified", pStat->st_mtime);
        nOut += printf("Cache-Control: max-age=%d\r\n", mxAge);
        nOut += printf("ETag: \"%s\"\r\n", zETag);
        nOut += printf("\r\n");
        fflush(stdout);
        /* LOG: ETag缓存命中 */
        MakeLogEntry(0, 470);
        return 1;
    }
    in = fopen(zFile,"rb");
    /* LOG: 用读取静态文件内容fopen()函数读取失败 */
    if( in==0 ) NotFound(480);
    if( rangeEnd>0 && rangeStart<pStat->st_size ){
        StartResponse("206 Partial Content");
        if( rangeEnd>=pStat->st_size ){
            rangeEnd = pStat->st_size-1;
        }
        nOut += printf("Content-Range: bytes %d-%d/%d\r\n",rangeStart, rangeEnd, (int)pStat->st_size);
        pStat->st_size = rangeEnd + 1 - rangeStart;
    }else{
        StartResponse("200 OK");
        rangeStart = 0;
    }
    nOut += DateTag("Last-Modified", pStat->st_mtime);
    nOut += printf("Cache-Control: max-age=%d\r\n", mxAge);
    nOut += printf("ETag: \"%s\"\r\n", zETag);
    nOut += printf("Content-type: %s; charset=utf-8\r\n",zContentType);
    nOut += printf("Content-length: %d\r\n\r\n",(int)pStat->st_size);
    fflush(stdout);
    if( strcmp(zMethod,"HEAD")==0 ){
        /* LOG: 正常的响应头 */
        MakeLogEntry(0, 2);
        fclose(in);
        fflush(stdout);
        return 1;
    }
    if( useTimeout ) alarm(30 + pStat->st_size/1000);
#ifdef linux
    {
    off_t offset = rangeStart;
    nOut += sendfile(fileno(stdout), fileno(in), &offset, pStat->st_size);
  }
#else
    xferBytes(in, stdout, (int)pStat->st_size, rangeStart);
#endif
    fclose(in);
    return 0;
}

/*
 * CGI或SCGI脚本已经运行,并且正在通过"in"通道发送其回复.
 * 将此响应处理为适当的HTTP响应.
 * 完成后关闭"in"通道.
*/
static void CgiHandleReply(FILE *in){
    int seenContentLength = 0;   /* 如果Content-length:头可见,则为True */
    int contentLength = 0;       /* (响应)内容长度 */
    size_t nRes = 0;             /* 有效载荷字节数 */
    size_t nMalloc = 0;          /* 分配给aRes的字节空间 */
    char *aRes = 0;              /* 有效载荷 */
    int c;                       /* 来自"in"通道的下一个字符 */
    char *z;                     /* 指向zLine内部的东西的指针 */
    int iStatus = 0;             /* 回复(响应)状态代码 */
    char zLine[1000];            /* CGI脚本的一行回复 */

/*提请系统软中断?*/
    if( useTimeout ){
        /*
         *禁用超时,这样我们就可以实现挂起的GET(Hanging-GET)或长轮询(long-poll)风格的CGIs.
         *RLIMIT_CPU将作为一种安全机制,帮助防止CGI失控
        */
        alarm(0);
    }
    while( fgets(zLine,sizeof(zLine),in) && !isspace((unsigned char)zLine[0]) ){
        if( strncasecmp(zLine,"Location:",9)==0 ){
            StartResponse("302 Redirect");
            RemoveNewline(zLine);
            z = &zLine[10];
            while( isspace(*(unsigned char*)z) ){ z++; }
            nOut += printf("Location: %s\r\n",z);
            rangeEnd = 0;
        }else if( strncasecmp(zLine,"Status:",7)==0 ){
            int i;
            for(i=7; isspace((unsigned char)zLine[i]); i++){}
            nOut += printf("%s %s", zProtocol, &zLine[i]);
            strncpy(zReplyStatus, &zLine[i], 3);
            zReplyStatus[3] = 0;
            iStatus = atoi(zReplyStatus);
            if( iStatus!=200 ) rangeEnd = 0;
            statusSent = 1;
        }else if( strncasecmp(zLine, "Content-length:", 15)==0 ){
            seenContentLength = 1;
            contentLength = atoi(zLine+15);
        }else{
            size_t nLine = strlen(zLine);
            if( nRes+nLine >= nMalloc ){
                nMalloc += nMalloc + nLine*2;
                aRes = realloc(aRes, nMalloc+1);
                if( aRes==0 ){
                    Malfunction(600, "Out of memory: %d bytes", nMalloc);
                }
            }
            memcpy(aRes+nRes, zLine, nLine);
            nRes += nLine;
        }
    }

    /* 把其他的东西都抄过来,不要改动或分析.*/
    if( rangeEnd>0 && seenContentLength && rangeStart<contentLength ){
        StartResponse("206 Partial Content");
        if( rangeEnd>=contentLength ){
            rangeEnd = contentLength-1;
        }
        nOut += printf("Content-Range: bytes %d-%d/%d\r\n",
                       rangeStart, rangeEnd, contentLength);
        contentLength = rangeEnd + 1 - rangeStart;
    }else{
        StartResponse("200 OK");
    }
    if( nRes>0 ){
        aRes[nRes] = 0;
        printf("%s", aRes);
        nOut += nRes;
        nRes = 0;
    }
    if( iStatus==304 ){
        nOut += printf("\r\n\r\n");
    }else if( seenContentLength ){
        nOut += printf("Content-length: %d\r\n\r\n", contentLength);
        xferBytes(in, stdout, contentLength, rangeStart);
    }else{
        while( (c = getc(in))!=EOF ){
            if( nRes>=nMalloc ){
                nMalloc = nMalloc*2 + 1000;
                aRes = realloc(aRes, nMalloc+1);
                if( aRes==0 ){
                    Malfunction(610, "Out of memory: %d bytes", nMalloc);
                }
            }
            aRes[nRes++] = c;
        }
        if( nRes ){
            aRes[nRes] = 0;
            nOut += printf("Content-length: %d\r\n\r\n%s", (int)nRes, aRes);
        }else{
            nOut += printf("Content-length: 0\r\n\r\n");
        }
    }
    free(aRes);
    fclose(in);
}

/*向zFile标识的主机发送SCGI请求并处理响应.*/
static void SendScgiRequest(const char *zFile, const char *zScript){
    FILE *in;
    FILE *s;
    char *z;
    char *zHost;
    char *zPort = 0;
    char *zRelight = 0;
    char *zFallback = 0;
    int rc;
    int iSocket = -1;
    struct addrinfo hints;
    struct addrinfo *ai = 0;
    struct addrinfo *p;
    char *zHdr;
    size_t nHdr = 0;
    size_t nHdrAlloc;
    int i;
    char zLine[1000];
    char zExtra[1000];
    in = fopen(zFile, "rb");
    if( in==0 ){
        Malfunction(700, "未能打开文件: \"%s\"\n", zFile);
    }
    if( fgets(zLine, sizeof(zLine)-1, in)==0 ){
        Malfunction(701, "未能读文件: \"%s\"\n", zFile);
    }
    if( strncmp(zLine,"SCGI ",5)!=0 ){
        Malfunction(702, "misformatted SCGI spec \"%s\"\n", zFile);
    }
    z = zLine+5;
    zHost = GetFirstElement(z,&z);
    zPort = GetFirstElement(z,0);
    if( zHost==0 || zHost[0]==0 || zPort==0 || zPort[0]==0 ){
        Malfunction(703, "misformatted SCGI spec \"%s\"\n", zFile);
    }
    while( fgets(zExtra, sizeof(zExtra)-1, in) ){
        char *zCmd = GetFirstElement(zExtra,&z);
        if( zCmd==0 ) continue;
        if( zCmd[0]=='#' ) continue;
        RemoveNewline(z);
        if( strcmp(zCmd, "relight:")==0 ){
            free(zRelight);
            zRelight = StrDup(z);
            continue;
        }
        if( strcmp(zCmd, "fallback:")==0 ){
            free(zFallback);
            zFallback = StrDup(z);
            continue;
        }
        Malfunction(704, "unrecognized line in SCGI spec: \"%s %s\"\n",
                    zCmd, z ? z : "");
    }
    fclose(in);
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    rc = getaddrinfo(zHost,zPort,&hints,&ai);
    if( rc ){
        Malfunction(704, "cannot resolve SCGI server name %s:%s\n%s\n",
                    zHost, zPort, gai_strerror(rc));
    }
    while(1){  /* Exit via break */
        for(p=ai; p; p=p->ai_next){
            iSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
            if( iSocket<0 ) continue;
            if( connect(iSocket,p->ai_addr,p->ai_addrlen)>=0 ) break;
            close(iSocket);
        }
        if( iSocket<0 || (s = fdopen(iSocket,"r+"))==0 ){
            if( iSocket>=0 ) close(iSocket);
            if( zRelight ){
                rc = system(zRelight);
                if( rc ){
                    Malfunction(721,"Relight failed with %d: \"%s\"\n",
                                rc, zRelight);
                }
                free(zRelight);
                zRelight = 0;
                sleep(1);
                continue;
            }
            if( zFallback ){
                struct stat statbuf;
                int rc;
                memset(&statbuf, 0, sizeof(statbuf));
                if( chdir(zDir) ){
                    char zBuf[1000];
                    Malfunction(720, /* LOG: chdir() failed */
                                "cannot chdir to [%s] from [%s]",
                                zDir, getcwd(zBuf,999));
                }
                rc = stat(zFallback, &statbuf);
                if( rc==0 && S_ISREG(statbuf.st_mode) && access(zFallback,R_OK)==0 ){
                    closeConnection = 1;
                    rc = SendFile(zFallback, (int)strlen(zFallback), &statbuf);
                    free(zFallback);
                    /*成功送出给(socket)客户端?*/
                    exit(0);
                }else{
                    Malfunction(706, "坏的回退文件: \"%s\"\n", zFallback);
                }
            }
            Malfunction(707, "无法打开到SCGI服务器%s的套接字\n",zScript);
        }
        break;
    }

    nHdrAlloc = 0;
    zHdr = 0;
    if( zContentLength==0 ) zContentLength = "0";
    zScgi = "1";
    for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
        int n1, n2;
        if( cgienv[i].pzEnvValue[0]==0 ) continue;
        n1 = (int)strlen(cgienv[i].zEnvName);
        n2 = (int)strlen(*cgienv[i].pzEnvValue);
        if( n1+n2+2+nHdr >= nHdrAlloc ){
            nHdrAlloc = nHdr + n1 + n2 + 1000;
            zHdr = realloc(zHdr, nHdrAlloc);
            if( zHdr==0 ){
                Malfunction(706, "内存溢出");
            }
        }
        memcpy(zHdr+nHdr, cgienv[i].zEnvName, n1);
        nHdr += n1;
        zHdr[nHdr++] = 0;
        memcpy(zHdr+nHdr, *cgienv[i].pzEnvValue, n2);
        nHdr += n2;
        zHdr[nHdr++] = 0;
    }
    zScgi = 0;
    fprintf(s,"%d:",(int)nHdr);
    fwrite(zHdr, 1, nHdr, s);
    fprintf(s,",");
    free(zHdr);
    if( zMethod[0]=='P'
        && atoi(zContentLength)>0
        && (in = fopen(zTmpNam,"r"))!=0 ){
        size_t n;
        while( (n = fread(zLine,1,sizeof(zLine),in))>0 ){
            fwrite(zLine, 1, n, s);
        }
        fclose(in);
    }
    fflush(s);
    CgiHandleReply(s);
}

/*
 *
 * 此例程处理标准输入上的单个HTTP请求,并将应答发送到标准输出.
 * 如果参数为1,则表示在当前请求完成后,我们应该关闭套接字而不处理其他HTTP请求.
 * 0表示允许我们保持连接打开并处理其他请求.
 * 即使参数为0,此例程也可以选择关闭连接.
 *
 * 如果连接应该关闭,这个例程调用exit(),因此永远不会返回.
 * 如果此例程返回,则表示另一个HTTP请求可能出现在连接上.
*/
void ProcessOneRequest(int forceClose){
    int i, j, j0;
    char *z;                  /* 用于解析字符串 */
    struct stat statbuf;      /* 有关要检索的文件的信息 */
    FILE *in;                 /* 用于读取CGI脚本 */
#ifdef LOG_HEADER
    FILE *hdrLog = 0;         /* 完整(请求)头内容的日志文件 */
#endif
    char zLine[1000];         /* 输入行或形成名称的缓冲区 */

    /* 将目录更改为HTTP文件系统的根目录 */

    if( chdir(zRoot[0] ? zRoot : "/")!=0 ){
        char zBuf[1000];
        /* LOG: chdir() failed */
        Malfunction(190, "无法从目录[%s]切换到目录[%s]",getcwd(zBuf,999),zRoot);
    }
    nRequest++;

    /*我们必须在15秒内收到完整的报头*/
    signal(SIGALRM, Timeout);
    signal(SIGSEGV, Timeout);
    signal(SIGPIPE, Timeout);
    signal(SIGXCPU, Timeout);
    if( useTimeout ) alarm(15);

    /* 获取请求的第一行并解析出方法(如post/get)、脚本和协议.*/
    if( fgets(zLine,sizeof(zLine),stdin)==0 ){
        exit(0);
    }
    gettimeofday(&beginTime, 0);
    omitLog = 0;
    nIn += strlen(zLine);

    /* 解析HTTP请求的第一行 */
    zMethod = StrDup(GetFirstElement(zLine,&z));
    zRealScript = zScript = StrDup(GetFirstElement(z,&z));
    zProtocol = StrDup(GetFirstElement(z,&z));
    if( zProtocol==0 || strncmp(zProtocol,"HTTP/",5)!=0 || strlen(zProtocol)!=8 ){
        StartResponse("400 Bad Request");
        nOut += printf(
                "Content-type: text/plain; charset=utf-8\r\n"
                "\r\n"
                "This server does not understand the requested protocol\n"
        );
        /* LOG: HTTP头中的协议错误 */
        MakeLogEntry(0, 200);
        exit(0);
    }
    /* LOG: 空请求URI */
    if( zScript[0]!='/' ) NotFound(210);
    while( zScript[1]=='/' ){
        zScript++;
        zRealScript++;
    }
    if( forceClose ){
        closeConnection = 1;
    }else if( zProtocol[5]<'1' || zProtocol[7]<'1' ){
        closeConnection = 1;
    }

    /* 这个非常简单的服务器只理解GET、POST和HEAD方法 */
    if( strcmp(zMethod,"GET")!=0 && strcmp(zMethod,"POST")!=0
        && strcmp(zMethod,"HEAD")!=0 ){
        StartResponse("501 Not Implemented");
        nOut += printf(
                "Content-type: text/plain; charset=utf-8\r\n"
                "\r\n"
                "The %s method is not implemented on this server.\n",
                zMethod);
        /* LOG: 未知请求方法 */
        MakeLogEntry(0, 220);
        exit(0);
    }

/*
* 如果有日志文件（如果是zLogFile=0）,如果http请求第一行中的路径名包含魔术字符串"FullHeaderLog",则将完整的头文本写入文件%s（zLogFile）-hdr.
* 覆盖文件.
* 这仅适用于协议调试,并且仅在使用-DLOG_HEADER=1选项编译althttpd时启用.
*/
#ifdef LOG_HEADER
    if( zLogFile
   && strstr(zScript,"FullHeaderLog")!=0
   && strlen(zLogFile)<sizeof(zLine)-50
  ){
    sprintf(zLine, "%s-hdr", zLogFile);
    hdrLog = fopen(zLine, "wb");
  }
#endif


    /* 获取第一行后面的所有可选字段.*/
    zCookie = 0;
    zAuthType = 0;
    zRemoteUser = 0;
    zReferer = 0;
    zIfNoneMatch = 0;
    zIfModifiedSince = 0;
    rangeEnd = 0;
    while( fgets(zLine,sizeof(zLine),stdin) ){
        char *zFieldName;
        char *zVal;

#ifdef LOG_HEADER
        if( hdrLog ) fprintf(hdrLog, "%s", zLine);
#endif
        nIn += strlen(zLine);
        zFieldName = GetFirstElement(zLine,&zVal);
        if( zFieldName==0 || *zFieldName==0 ) break;
        RemoveNewline(zVal);
        if( strcasecmp(zFieldName,"User-Agent:")==0 ){
            zAgent = StrDup(zVal);
        }else if( strcasecmp(zFieldName,"Accept:")==0 ){
            zAccept = StrDup(zVal);
        }else if( strcasecmp(zFieldName,"Accept-Encoding:")==0 ){
            zAcceptEncoding = StrDup(zVal);
        }else if( strcasecmp(zFieldName,"Content-length:")==0 ){
            zContentLength = StrDup(zVal);
        }else if( strcasecmp(zFieldName,"Content-type:")==0 ){
            zContentType = StrDup(zVal);
        }else if( strcasecmp(zFieldName,"Referer:")==0 ){
            zReferer = StrDup(zVal);
            if( strstr(zVal, "devids.net/")!=0 ){
                zReferer = "devids.net.smut";
                /* LOG: 引用者 devids.net*/
                Forbidden(230);
            }
        }else if( strcasecmp(zFieldName,"Cookie:")==0 ){
            zCookie = StrAppend(zCookie,"; ",zVal);
        }else if( strcasecmp(zFieldName,"Connection:")==0 ){
            if( strcasecmp(zVal,"close")==0 ){
                closeConnection = 1;
            }else if( !forceClose && strcasecmp(zVal, "keep-alive")==0 ){
                closeConnection = 0;
            }
        }else if( strcasecmp(zFieldName,"Host:")==0 ){
            int inSquare = 0;
            char c;
            if( sanitizeString(zVal) ){
                /* LOG: 主机中的非法内容：参数 */
                Forbidden(240);
            }
            zHttpHost = StrDup(zVal);
            zServerPort = zServerName = StrDup(zHttpHost);
            while( zServerPort && (c = *zServerPort)!=0
                   && (c!=':' || inSquare) ){
                if( c=='[' ) inSquare = 1;
                if( c==']' ) inSquare = 0;
                zServerPort++;
            }
            if( zServerPort && *zServerPort ){
                *zServerPort = 0;
                zServerPort++;
            }
            if( zRealPort ){
                zServerPort = StrDup(zRealPort);
            }
        }else if( strcasecmp(zFieldName,"Authorization:")==0 ){
            zAuthType = GetFirstElement(StrDup(zVal), &zAuthArg);
        }else if( strcasecmp(zFieldName,"If-None-Match:")==0 ){
            zIfNoneMatch = StrDup(zVal);
        }else if( strcasecmp(zFieldName,"If-Modified-Since:")==0 ){
            zIfModifiedSince = StrDup(zVal);
        }else if( strcasecmp(zFieldName,"Range:")==0
                  && strcmp(zMethod,"GET")==0 ){
            int x1 = 0, x2 = 0;
            int n = sscanf(zVal, "bytes=%d-%d", &x1, &x2);
            if( n==2 && x1>=0 && x2>=x1 ){
                rangeStart = x1;
                rangeEnd = x2;
            }else if( n==1 && x1>0 ){
                rangeStart = x1;
                rangeEnd = 0x7fffffff;
            }
        }
    }
#ifdef LOG_HEADER
    if( hdrLog ) fclose(hdrLog);
#endif

    /* 不允许来自某些客户端的请求 */
    if( zAgent ){
        /*黑名单*/
        const char *azDisallow[] = {
                "Windows 9",
                "Download Master",
                "Ezooms/",
                "HTTrace",
                "AhrefsBot",
                "MicroMessenger",
                "OPPO A33 Build",
                "SemrushBot",
                "MegaIndex.ru",
                "MJ12bot",
                "Chrome/0.A.B.C",
                "Neevabot/",
                "BLEXBot/",
        };
        size_t ii;
        for(ii=0; ii<sizeof(azDisallow)/sizeof(azDisallow[0]); ii++){
            if( strstr(zAgent,azDisallow[ii])!=0 ){
                /* LOG: 不被允许的用户代理 */
                Forbidden(250);
            }
        }
#if 0
        /* 蜘蛛攻击2019-04-24 */
    if( strcmp(zAgent,
            "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 "
            "(KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36")==0 ){
        /* LOG: 不被允许的用户代理 (20190424) */
      Forbidden(251);
    }
#endif
    }
#if 0
    if( zReferer ){
    static const char *azDisallow[] = {
      "skidrowcrack.com",
      "hoshiyuugi.tistory.com",
      "skidrowgames.net",
    };
    int i;
    for(i=0; i<sizeof(azDisallow)/sizeof(azDisallow[0]); i++){
      if( strstr(zReferer, azDisallow[i])!=0 ){
          /* LOG: 不允许的引用页 */
        NotFound(260);
      }
    }
  }
#endif

    /*
     * 额外努力获取有效的服务器名称和端口号.
     * 只有Netscape提供此信息.
     * 如果浏览器是Internet Explorer,那么我们必须自己找出信息.
    */
    if( zServerName==0 ){
        zServerName = SafeMalloc( 100 );
        gethostname(zServerName,100);
    }
    if( zServerPort==0 || *zServerPort==0 ){
        zServerPort = DEFAULT_PORT;
    }

    /* 从请求文件的末尾删除查询字符串.*/
    for(z=zScript; *z && *z!='?'; z++){}
    if( *z=='?' ){
        zQuerySuffix = StrDup(z);
        *z = 0;
    }else{
        zQuerySuffix = "";
    }
    zQueryString = *zQuerySuffix ? &zQuerySuffix[1] : zQuerySuffix;

    /*
     * 创建一个文件来保存(POST请求)查询后的数据(如果有的话).
     * 我们必须这样做.
     * 我们不能只将文件描述符传递给子进程,因为fgets()函数可能已经将POST数据的一部分读入了它的内部缓冲区.
    */
    if( zMethod[0]=='P' && zContentLength!=0 ){
        size_t len = atoi(zContentLength);
        FILE *out;
        char *zBuf;
        int n;

        if( len>MAX_CONTENT_LENGTH ){
            StartResponse("500 Request too large");
            nOut += printf(
                    "Content-type: text/plain; charset=utf-8\r\n"
                    "\r\n"
                    "提交太大数据\n"
            );
            /* LOG: 请求(数据)太大 */
            MakeLogEntry(0, 270);
            exit(0);
        }
        rangeEnd = 0;
        sprintf(zTmpNamBuf, "/tmp/-post-data-XXXXXX");
        zTmpNam = zTmpNamBuf;
        if( mkstemp(zTmpNam)<0 ){
            /* LOG: 建立唯一的临时文件[mkstemp()]失败 */
            Malfunction(280,"无法创建用于存储POST数据的临时文件");
        }
        out = fopen(zTmpNam,"wb");
        if( out==0 ){
            StartResponse("500 无法创建/tmp文件");
            nOut += printf(
                    "Content-type: text/plain; charset=utf-8\r\n"
                    "\r\n"
                    "无法打开\"%s\"进行写入\n", zTmpNam
            );
            /* LOG: 无法为POST(数据)创建临时文件 */
            MakeLogEntry(0, 290);
            exit(0);
        }
        zBuf = SafeMalloc( len+1 );
        if( useTimeout ) alarm(15 + len/2000);
        n = fread(zBuf,1,len,stdin);
        nIn += n;
        fwrite(zBuf,1,n,out);
        free(zBuf);
        fclose(out);
    }

    /* 确保运行时间不要太长 */
    if( useTimeout ) alarm(10);

    /* 将脚本名称中所有不寻常的字符转换为"_".这是对各种攻击的防御,特别是XSS攻击.*/
    sanitizeString(zScript);

    /*
     * 不允许"/." 或 "/-"出现在实体名称中的任何位置.
     * 这可以防止涉及".."的攻击,还允许我们创建名称以 "-" 或 "."开头的文件和目录,这些文件和目录对Web服务器不可见.
     * 例外情况:根据RFC-5785标准允许使用"/.well-known/"前缀.
    */
    for(z=zScript; *z; z++){
        if( *z=='/' && (z[1]=='.' || z[1]=='-') ){
            if( strncmp(zScript,"/.well-known/",13)==0 && (z[1]!='.' || z[2]!='.') ){
                /* 例外情况：允许"/."和"/-",用于带有"/.well-known/"的URL.但不允许"/..". */
                continue;
            }
            /* LOG: Path元素以"."或"-"开头 */
            NotFound(300);
        }
    }

    /*
     * 找出文件系统的根目录.
     * 如果存在HTTP_HOST参数(存储在zHttpHost中),则从末尾删除端口号(如果有),将所有字符转换为小写,并将非字母数字字符(包括".")转换为"_".
     * 然后试着找到一个具有该名称和扩展名.website的目录.
     * 如果找不到,请查找"default.website".
    */
    if( zScript[0]!='/' ){
        /* LOG: URI不以"/"开头 */
        NotFound(310);
    }
    if( strlen(zRoot)+40 >= sizeof(zLine) ){
        /* LOG: URI太长 */
        NotFound(320);
    }
    if( zHttpHost==0 || zHttpHost[0]==0 ){
        /* LOG: 缺少主机:参数 */
        NotFound(330);
    }else if( strlen(zHttpHost)+strlen(zRoot)+10 >= sizeof(zLine) ){
        /* LOG: 主机参数太长 */
        NotFound(340);
    }else{
        sprintf(zLine, "%s/%s", zRoot, zHttpHost);
        for(i=strlen(zRoot)+1; zLine[i] && zLine[i]!=':'; i++){
            unsigned char c = (unsigned char)zLine[i];
            if( !isalnum(c) ){
                if( c=='.' && (zLine[i+1]==0 || zLine[i+1]==':') ){
                    /* 如果客户端发送的FQDN末尾带有"."(例如:"sqlite.org.",而不是"sqlite.org"),则从文档根目录名称中省略最后的"." */
                    break;
                }
                zLine[i] = '_';
            }else if( isupper(c) ){
                zLine[i] = tolower(c);
            }
        }
        strcpy(&zLine[i], ".website");
    }
    if( stat(zLine,&statbuf) || !S_ISDIR(statbuf.st_mode) ){
        sprintf(zLine, "%s/default.website", zRoot);
        if( stat(zLine,&statbuf) || !S_ISDIR(statbuf.st_mode) ){
            if( standalone ){
                sprintf(zLine, "%s", zRoot);
            }else{
                /* LOG: *.website权限 */
                NotFound(350);
            }
        }
    }
    zHome = StrDup(zLine);

    /* 将目录更改为HTTP文件系统的根目录 */
    if( chdir(zHome)!=0 ){
        char zBuf[1000];
        /* LOG: chdir() failed */
        Malfunction(360, "cannot chdir to [%s] from [%s]",zHome, getcwd(zBuf,999));
    }

    /*
     * 在文件系统中找到文件.
     * 为了找到它,我们可能需要附加一个像"/home"或"/index.html"或"/index.cgi"这样的名称.
     * 任何多余的路径信息都会放入zPathInfo变量.
    */
    j = j0 = (int)strlen(zLine);
    i = 0;
    while( zScript[i] ){
        while( zScript[i] && (i==0 || zScript[i]!='/') ){
            zLine[j] = zScript[i];
            i++; j++;
        }
        zLine[j] = 0;
        if( stat(zLine,&statbuf)!=0 ){
            int stillSearching = 1;
            while( stillSearching && i>0 && j>j0 ){
                while( j>j0 && zLine[j-1]!='/' ){ j--; }
                strcpy(&zLine[j-1], "/not-found.html");
                if( stat(zLine,&statbuf)==0 && S_ISREG(statbuf.st_mode)
                    && access(zLine,R_OK)==0 ){
                    zRealScript = StrDup(&zLine[j0]);
                    /* LOG: 从定向到未发现(这个文件) */
                    Redirect(zRealScript, 302, 1, 370);
                    return;
                }else{
                    j--;
                }
            }
            /* LOG: 找不到URI */
            if( stillSearching ) NotFound(380);
            break;
        }
        if( S_ISREG(statbuf.st_mode) ){
            if( access(zLine,R_OK) ){
                /* LOG: 文件不可读 */
                NotFound(390);
            }
            zRealScript = StrDup(&zLine[j0]);
            break;
        }
        if( zScript[i]==0 || zScript[i+1]==0 ){
            static const char *azIndex[] = { "/home", "/index.html", "/index.cgi" };
            int k = j>0 && zLine[j-1]=='/' ? j-1 : j;
            unsigned int jj;
            for(jj=0; jj<sizeof(azIndex)/sizeof(azIndex[0]); jj++){
                strcpy(&zLine[k],azIndex[jj]);
                if( stat(zLine,&statbuf)!=0 ) continue;
                if( !S_ISREG(statbuf.st_mode) ) continue;
                if( access(zLine,R_OK) ) continue;
                break;
            }
            if( jj>=sizeof(azIndex)/sizeof(azIndex[0]) ){
                /* LOG: URI is a directory w/o index.html */
                NotFound(400);
            }
            zRealScript = StrDup(&zLine[j0]);
            if( zScript[i]==0 ){
                /*
                 * 如果请求的URL没有以"/"结尾,但我们必须附加"index.html",则需要重定向.
                 * 否则,传递的文档中的任何相对URLs都将不正确.
                */
                /* LOG: 重定向以添加尾随 / */
                Redirect(zRealScript,301,1,410);
                return;
            }
            break;
        }
        zLine[j] = zScript[i];
        i++; j++;
    }
    zFile = StrDup(zLine);
    zPathInfo = StrDup(&zScript[i]);
    lenFile = strlen(zFile);
    zDir = StrDup(zFile);
    for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){};
    if( i==0 ){
        strcpy(zDir,"/");
    }else{
        zDir[i] = 0;
    }

    /* 检查是否有授权文件.如果有,处理它. */
    sprintf(zLine, "%s/-auth", zDir);
    if( access(zLine,R_OK)==0 && !CheckBasicAuthorization(zLine) ) return;

    /* 采取适当措施 可执行脚本? */
    if( (statbuf.st_mode & 0100)==0100 && access(zFile,X_OK)==0 ){
        /* 不带目录前缀的文件名 */
        char *zBaseFilename;

        /* 如果CGI脚本可由除其所有者以外的任何人写入,则中止并返回错误.*/
        if( statbuf.st_mode & 0022 ){
            CgiScriptWritable();
        }

        /* 如果它是可执行的,它必须是一个CGI程序.首先将目录更改为保存程序的目录.*/
        if( chdir(zDir) ){
            char zBuf[1000];
            /* LOG: chdir() failed */
            Malfunction(420,"cannot chdir to [%s] from [%s]",zDir, getcwd(zBuf,999));
        }

        /* 计算CGI脚本的基本文件名 */
        for(i=strlen(zFile)-1; i>=0 && zFile[i]!='/'; i--){}
        zBaseFilename = &zFile[i+1];

        /* 适当地设置环境.*/
        putenv("GATEWAY_INTERFACE=CGI/1.0");
        for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
            if( *cgienv[i].pzEnvValue ){
                SetEnv(cgienv[i].zEnvName,*cgienv[i].pzEnvValue);
            }
        }
        if( useHttps ){
            putenv("HTTPS=on");
            putenv("REQUEST_SCHEME=https");
        }else{
            putenv("REQUEST_SCHEME=http");
        }

        /* 对于POST方法,所有输入都已写入一个临时文件,因此我们必须将该文件中的输入重定向到CGI脚本.*/
        if( zMethod[0]=='P' ){
            if( dup(0)<0 ){
                /* LOG: dup(0) failed */
                Malfunction(430,"无法复制文件描述符0");
            }
            close(0);
            open(zTmpNam, O_RDONLY);
        }

        if( strncmp(zBaseFilename,"nph-",4)==0 ){
            /*
             * 如果CGI脚本的名称以"nph-"开头,那么我们将处理一个"未解析头(non-parsed headers)"CGI脚本.
             * 只需exec()直接执行它,并让它处理自己生成的所有头文件.
            */
            execl(zBaseFilename,zBaseFilename,(char*)0);
            /* 注意:没有为nph脚本(nph- scripts)编写日志条目 */
            exit(0);
        }

        /*
         * 仅当此进程（服务器）要读取并扩充由CGI进程发送回来的头时,才转到这里.
         * 打开一个管道以接收来自CGI进程的输出.
         * 然后分叉CGI过程.
         * 一旦一切都完成了,我们应该能够读取"in"流上的CGI输出.
        */
        {
            int px[2];
            if( pipe(px) ){
                /* LOG: 创建管道失败 */
                Malfunction(440,"无法为CGI程序创建管道");
            }
            /*叉出子进程进行处理*/
            if( fork()==0 ){
                close(px[0]);
                close(1);
                if( dup(px[1])!=1 ){
                    /* LOG: 管道复制失败 */
                    Malfunction(450,"无法将文件描述符%d复制到(管道)1",px[1]);
                }
                close(px[1]);
                for(i=3; close(i)==0; i++){}
                execl(zBaseFilename, zBaseFilename, (char*)0);
                exit(0);
            }
            close(px[1]);
            /*读取第一管道数据到输入流*/
            in = fdopen(px[0], "rb");
        }

        if( in==0 ){
            /*读无流数据*/
            CgiError();
        }else{
            CgiHandleReply(in);
        }
    }else if( lenFile>5 && strcmp(&zFile[lenFile-5],".scgi")==0 ){
        /*
         * 任何以".scgi"结尾的文件都假定为以下格式的文本:SCGI 主机名 端口
         * 打开到该主机的TCP/IP连接并向其发送SCGI请求
        */
        SendScgiRequest(zFile, zScript);
    }else if( countSlashes(zRealScript)!=countSlashes(zScript) ){
        /*如果静态内容的请求URI包含超过实际内容文件名的内容,则将其报告为404错误.*/
        /* LOG: 超过静态文件名的URI内容过多 */
        NotFound(460);
    }else{
        /*如果它是不可执行的,那么它必须是一个简单的文件,需要复制到输出.*/
        if( SendFile(zFile, lenFile, &statbuf) ) return;
    }
    fflush(stdout);
    /* LOG: 正常响应 */
    MakeLogEntry(0, 0);

    /* 下一个请求必须在30秒内到达,否则我们将关闭连接 */
    omitLog = 1;
    /*闹钟:30秒后没请求响应退出*/
    if( useTimeout ) alarm(30);
}

#define MAX_PARALLEL 50  /* 最大并发子进程数 */

/* IP地址的所有可能形式.需要绕过GCC严格的别名规则. */
typedef union {
    struct sockaddr sa;              /* 抽象超类 */
    struct sockaddr_in sa4;          /* IPv4 */
    struct sockaddr_in6 sa6;         /* IPv6 */
    struct sockaddr_storage sas;     /* 应为上述3项中的最大值 */
} address;

/*
* 实现在zPort端口侦听的HTTP服务的守护程序.
* 当新连接到达时,分叉一个子对象并让子对象从这个过程调用中返回.
* 子对象会处理这个请求.
* 父进程永远不会从这个进程返回.
* 在每个子进程运行时返回0.
* 如果无法建立侦听套接字,则返回非零.
*
*/
int http_server(const char *zPort, int localOnly){
    int listener[20];            /* 服务器套接字 */
    int connection;              /* 每个单独连接的插座 */
    fd_set readfds;              /* 选中链接[select()]的文件描述符集 */
    address inaddr;              /* 远程地址 */
    socklen_t lenaddr;           /* 远程地址数据结构的长度*/
    int child;                   /* 子进程的PID */
    int nchildren = 0;           /* 子进程数 */
    struct timeval delay;        /* 在选中链接[select()]内等待多长时间 */
    int opt = 1;                 /* 设置套接字选项(setsockopt)标志 */
    struct addrinfo sHints;      /* 地址信息提示内容的数据结构 */
    struct addrinfo *pAddrs, *p; /* 两个地址信息数据结构指针*/
    int rc;                      /* 结果代码 */
    int i, n;
    int maxFd = -1;

    memset(&sHints, 0, sizeof(sHints));
    if( ipv4Only ){
        sHints.ai_family = PF_INET;
        /*printf("ipv4 only\n");*/
    }else if( ipv6Only ){
        sHints.ai_family = PF_INET6;
        /*printf("ipv6 only\n");*/
    }else{
        sHints.ai_family = PF_UNSPEC;
    }
    sHints.ai_socktype = SOCK_STREAM;
    sHints.ai_flags = AI_PASSIVE;
    sHints.ai_protocol = 0;
    /*int getaddrinfo(const char* nodename,const char* servname,const struct addrinfo* hints,struct addrinfo** res);*/
    rc = getaddrinfo(localOnly ? "localhost": 0, zPort, &sHints, &pAddrs);
    if( rc ){
        fprintf(stderr, "无法获取地址信息: %s",rc!=EAI_SYSTEM ? gai_strerror(rc) : strerror(errno));
        return 1;
    }
    /*处理已经读取IP服务链表,创建->设置选项->绑定->侦听*/
    for(n=0, p=pAddrs; n<(int)(sizeof(listener)/sizeof(listener[0])) && p!=0;p=p->ai_next){
        /*根据服务IP链表创建多个socket(最多20个),并将文件描述符存放至侦听数组listener[20]*/
        listener[n] = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if( listener[n]>=0 ){
            /* 如果我们不能很好地终止,至少允许套接字被重用*/
            setsockopt(listener[n], SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));

#if defined(IPV6_V6ONLY)
            if( p->ai_family==AF_INET6 ){
                int v6only = 1;
                setsockopt(listener[n], IPPROTO_IPV6, IPV6_V6ONLY,&v6only, sizeof(v6only));
            }
#endif

            if( bind(listener[n], p->ai_addr, p->ai_addrlen)<0 ){
                printf("主机绑定失败: %s\n", strerror(errno));
                close(listener[n]);
                continue;
            }
            if( listen(listener[n], 20)<0 ){
                printf("启用侦听失败: %s\n", strerror(errno));
                close(listener[n]);
                continue;
            }
            n++;
        }
    }
    if( n==0 ){
        fprintf(stderr, "无法打开任何套接字\n");
        return 1;
    }

    while( 1 ){
        if( nchildren>MAX_PARALLEL ){
            /*如果连接太快,请减速*/
            sleep( nchildren-MAX_PARALLEL );
        }
        delay.tv_sec = 60;
        delay.tv_usec = 0;
        /*将你的套节字集合清空*/
        FD_ZERO(&readfds);
        for(i=0; i<n; i++){
            assert( listener[i]>=0 );
            /*加入你感兴趣的套节字到集合,这里是一个读数据的套节字listener[i]到readfds*/
            FD_SET( listener[i], &readfds);
            if( listener[i]>maxFd ) maxFd = listener[i];
        }
        /*轮询选中,检查套节字是否可读,有数据*/
        select( maxFd+1, &readfds, 0, 0, &delay);
        for(i=0; i<n; i++){
            /*检查listener[i]是否在这个readfds集合里面,select将更新这个集合,把其中不可读的套节字去掉,只保留符合条件的套节字在这个集合里面*/
            if( FD_ISSET(listener[i], &readfds) ){
                lenaddr = sizeof(inaddr);
                /*接受此socket为当前链接*/
                connection = accept(listener[i], &inaddr.sa, &lenaddr);
                if( connection>=0 ){
                    /*叉个子进程出来处理接收数据事宜*/
                    child = fork();
                    if( child!=0 ){
                        /*对父进程处理*/
                        if( child>0 ) nchildren++;
                        close(connection);
                        /* printf("subprocess %d started...\n", child); fflush(stdout); */
                    }else{
                        /*对子进程处理*/
                        int nErr = 0, fd;
                        close(0);
                        fd = dup(connection);
                        if( fd!=0 ) nErr++;
                        close(1);
                        fd = dup(connection);
                        if( fd!=1 ) nErr++;
                        close(connection);
                        return nErr;
                    }
                }
            }
            /* 销毁子进程 */
            while( (child = waitpid(0, 0, WNOHANG))>0 ){
                /*WNOHANG信号*/
                /* printf("process %d ends\n", child); fflush(stdout); */
                nchildren--;
            }
        }
    }
    /* NOT REACHED */
    exit(1);
}


int main(int argc, char **argv){
    int i;                    /* 循环计数器 */
    char *zPermUser = 0;      /* 使用此用户的权限运行守护程序 */
    const char *zPort = 0;    /* 实现HTTP服务器进程 */
    int useChrootJail = 1;    /* 如果此值为真(True)则转换(越狱)为root权限 */
    struct passwd *pwd = 0;   /* 有关用户的信息 */

    /*记录处理开始的时间.*/
    gettimeofday(&beginTime, 0);

    /*解释命令行参数*/
    while( argc>1 && argv[1][0]=='-' ){
        char *z = argv[1];
        char *zArg = argc>=3 ? argv[2] : "0";
        if( z[0]=='-' && z[1]=='-' ) z++;
        if( strcmp(z,"-user")==0 ){
            zPermUser = zArg;
        }else if( strcmp(z,"-root")==0 ){
            zRoot = zArg;
        }else if( strcmp(z,"-logfile")==0 ){
            zLogFile = zArg;
        }else if( strcmp(z,"-max-age")==0 ){
            mxAge = atoi(zArg);
        }else if( strcmp(z,"-max-cpu")==0 ){
            maxCpu = atoi(zArg);
        }else if( strcmp(z,"-https")==0 ){
            useHttps = atoi(zArg);
            zHttp = useHttps ? "https" : "http";
            if( useHttps ) zRemoteAddr = getenv("REMOTE_HOST");
        }else if( strcmp(z, "-port")==0 ){
            zPort = zArg;
            standalone = 1;
        }else if( strcmp(z, "-family")==0 ){
            if( strcmp(zArg, "ipv4")==0 ){
                ipv4Only = 1;
            }else if( strcmp(zArg, "ipv6")==0 ){
                ipv6Only = 1;
            }else{
                /* LOG: 未知IP协议族*/
                Malfunction(500,"未知IP协议: [%s]\n", zArg);
            }
        }else if( strcmp(z, "-jail")==0 ){
            /*启动为限制类型*/
            if( atoi(zArg)==0 ){
                useChrootJail = 0;
            }
        }else if( strcmp(z, "-debug")==0 ){
            if( atoi(zArg) ){
                useTimeout = 0;
            }
        }else if( strcmp(z, "-input")==0 ){
            if( freopen(zArg, "rb", stdin)==0 || stdin==0 ){
                /* LOG: 不能打开--input(输入流)文件 */
                Malfunction(501,"不能打开输入流文件: \"%s\"\n", zArg);
            }
        }else if( strcmp(z, "-datetest")==0 ){
            TestParseRfc822Date();
            printf("Ok\n");
            exit(0);
        }else{
            /* LOG:启动时未知的命令行参数 */
            Malfunction(510, "未知参数: [%s]\n", z);
        }
        argv += 2;
        argc -= 2;
    }

    if( zRoot==0 ){
        if( standalone ){
            zRoot = ".";
        }else{
            /* LOG: 缺少--root参数  */
            Malfunction(520, "--root参数未指定");
        }
    }

    /*将目录更改为HTTP文件系统的根目录.然后在那里建一个可供切换根目录(root)的狱(chroot jail).*/
    /*日志:切换(chdir())目录失败*/
    if( chdir(zRoot)!=0 ){
        Malfunction(530, "无法切换到目录: [%s]", zRoot);
    }

    /* 获取有关用户的信息(如果可用) */
    if( zPermUser ) pwd = getpwnam(zPermUser);

    /*如果有人要求的话,可以切换根目录(chroot jail)*/
    if( zPermUser && useChrootJail && getuid()==0 ){
        if( chroot(".")<0 ){
            /*日志:切换(chdir())目录失败*/
            Malfunction(540,"无法创建根目录");
        }else{
            zRoot = "";
        }
    }

    /* 如果需要,激活服务器 */
    if( zPort && http_server(zPort, 0) ){
        /*日志:服务器启动失败*/
        Malfunction(550, "无法启动服务器");
    }

#ifdef RLIMIT_CPU
    if( maxCpu>0 ){
    struct rlimit rlim;
    rlim.rlim_cur = maxCpu;
    rlim.rlim_max = maxCpu;
    setrlimit(RLIMIT_CPU, &rlim);
  }
#endif

    /* 删除根权限.*/
    if( zPermUser ){
        if( pwd ){
            if( setgid(pwd->pw_gid) ){
                /* 日志: 设置组编号(setgid())失败 */
                Malfunction(560, "无法将组id设置为:%d", pwd->pw_gid);
            }
            if( setuid(pwd->pw_uid) ){
                /* 日志: 设置用户编号(setuid())失败 */
                Malfunction(570, "无法将用户id设置为: %d", pwd->pw_uid);
            }
        }else{
            /* 日志: 未知用户 */
            Malfunction(580,"没有这样的用户: [%s]", zPermUser);
        }
    }
    if( getuid()==0 ){
        /* 日志: 无法作为超级(root)用户运行 */
        Malfunction(590, "无法作为超级用户运行");
    }

    /* 获取发出请求的IP地址*/
    if( zRemoteAddr==0 ){
        address remoteAddr;
        unsigned int size = sizeof(remoteAddr);
        char zHost[NI_MAXHOST];
        if( getpeername(0, &remoteAddr.sa, &size)>=0 ){
            getnameinfo(&remoteAddr.sa, size, zHost, sizeof(zHost), 0, 0,NI_NUMERICHOST);
            zRemoteAddr = StrDup(zHost);
        }
    }
    if( zRemoteAddr!=0
        && strncmp(zRemoteAddr, "::ffff:", 7)==0
        && strchr(zRemoteAddr+7, ':')==0
        && strchr(zRemoteAddr+7, '.')!=0
            ){
        zRemoteAddr += 7;
    }

    /* 处理输入流,响应请求 */
    for(i=0; i<100; i++){
        ProcessOneRequest(0);
    }
    ProcessOneRequest(1);
    exit(0);
}

#if 0

/* 将以下文本复制/粘贴到SQLite中,以生成描述所有错误代码的外部参照表.*/
BEGIN;
CREATE TABLE IF NOT EXISTS xref(lineno INTEGER PRIMARY KEY, desc TEXT);
DELETE FROM Xref;
INSERT INTO xref VALUES(100,'Malloc() failed');
INSERT INTO xref VALUES(110,'Not authorized');
INSERT INTO xref VALUES(120,'CGI Error');
INSERT INTO xref VALUES(130,'Timeout');
INSERT INTO xref VALUES(140,'CGI script is writable');
INSERT INTO xref VALUES(150,'Cannot open -auth file');
INSERT INTO xref VALUES(160,'http request on https-only page');
INSERT INTO xref VALUES(170,'-auth redirect');
INSERT INTO xref VALUES(180,'malformed entry in -auth file');
INSERT INTO xref VALUES(190,'chdir() failed');
INSERT INTO xref VALUES(200,'bad protocol in HTTP header');
INSERT INTO xref VALUES(210,'Empty request URI');
INSERT INTO xref VALUES(220,'Unknown request method');
INSERT INTO xref VALUES(230,'Referrer is devids.net');
INSERT INTO xref VALUES(240,'Illegal content in HOST: parameter');
INSERT INTO xref VALUES(250,'Disallowed user agent');
INSERT INTO xref VALUES(260,'Disallowed referrer');
INSERT INTO xref VALUES(270,'Request too large');
INSERT INTO xref VALUES(280,'mkstemp() failed');
INSERT INTO xref VALUES(290,'cannot create temp file for POST content');
INSERT INTO xref VALUES(300,'Path element begins with . or -');
INSERT INTO xref VALUES(310,'URI does not start with /');
INSERT INTO xref VALUES(320,'URI too long');
INSERT INTO xref VALUES(330,'Missing HOST: parameter');
INSERT INTO xref VALUES(340,'HOST parameter too long');
INSERT INTO xref VALUES(350,'*.website permissions');
INSERT INTO xref VALUES(360,'chdir() failed');
INSERT INTO xref VALUES(370,'redirect to not-found page');
INSERT INTO xref VALUES(380,'URI not found');
INSERT INTO xref VALUES(390,'File not readable');
INSERT INTO xref VALUES(400,'URI is a directory w/o index.html');
INSERT INTO xref VALUES(410,'redirect to add trailing /');
INSERT INTO xref VALUES(420,'chdir() failed');
INSERT INTO xref VALUES(430,'dup(0) failed');
INSERT INTO xref VALUES(440,'pipe() failed');
INSERT INTO xref VALUES(450,'dup(1) failed');
INSERT INTO xref VALUES(460,'Excess URI content past static file name');
INSERT INTO xref VALUES(470,'ETag Cache Hit');
INSERT INTO xref VALUES(480,'fopen() failed for static content');
INSERT INTO xref VALUES(2,'Normal HEAD reply');
INSERT INTO xref VALUES(0,'Normal reply');
INSERT INTO xref VALUES(500,'unknown IP protocol');
INSERT INTO xref VALUES(501,'cannot open --input file');
INSERT INTO xref VALUES(510,'unknown command-line argument on launch');
INSERT INTO xref VALUES(520,'--root argument missing');
INSERT INTO xref VALUES(530,'chdir() failed');
INSERT INTO xref VALUES(540,'chroot() failed');
INSERT INTO xref VALUES(550,'server startup failed');
INSERT INTO xref VALUES(560,'setgid() failed');
INSERT INTO xref VALUES(570,'setuid() failed');
INSERT INTO xref VALUES(580,'unknown user');
INSERT INTO xref VALUES(590,'cannot run as root');
INSERT INTO xref VALUES(600,'malloc() failed');
INSERT INTO xref VALUES(610,'malloc() failed');
COMMIT;
#endif /* SQL */
